const util = {}

// 类型判断
const ctype = util || {};
(function (obj) {
  function typev (obj) {
    return Object.prototype.toString.call(obj).split(/^\[object ([a-zA-Z]*)\]/)[1].toLowerCase()
  }

  function isArray (obj) {
    return typev(obj) === 'array'
  }

  function isDate (obj) {
    return typev(obj) === 'date'
  }

  function isMoment (obj) {
    return obj.constructor && obj.constructor.name === 'Moment'
  }

  function isNumber (obj) {
    return typev(obj) === 'number' && !isNaN(obj)
  }

  function isString (obj) {
    return typev(obj) === 'string'
  }

  function isBoolean (obj) {
    return typev(obj) === 'boolean'
  }

  function isObject (obj) {
    return typev(obj) === 'object'
  }

  function isNull (obj) {
    return typev(obj) === 'null'
  }

  function isUndefined (obj) {
    return typev(obj) === 'undefined'
  }

  function isMouseEvent (obj) {
    return typev(obj) === 'mouseevent'
  }

  function isFunction (obj) {
    return typev(obj) === 'function'
  }

  function isLocation (obj) {
    return typev(obj) === 'location'
  }

  var getProto = Object.getPrototypeOf
  var class2type = {}
  var toString = class2type.toString
  var hasOwn = class2type.hasOwnProperty
  var fnToString = hasOwn.toString
  var ObjectFunctionString = fnToString.call(Object)

  function isPlainObject (obj) {
    var proto, Ctor
    if (!obj || toString.call(obj) !== '[object Object]') {
      return false
    }
    proto = getProto(obj)
    if (!proto) return true

    Ctor = hasOwn.call(proto, 'constructor') && proto.constructor
    return typeof Ctor === 'function' && fnToString.call(Ctor) === ObjectFunctionString
  }

  obj.typev = typev
  obj.isArray = isArray
  obj.isDate = isDate
  obj.isMoment = isMoment
  obj.isNumber = isNumber
  obj.isString = isString
  obj.isBoolean = isBoolean
  obj.isObject = isObject
  obj.isNull = isNull
  obj.isUndefined = isUndefined
  obj.isMouseEvent = isMouseEvent
  obj.isFunction = isFunction
  obj.isPlainObject = isPlainObject
  obj.isLocation = isLocation
})(ctype)

// 数据处理
const cdata = util || {};
(function (obj) {
  /**
   * 数组遍历
   * @param {Array} arr 数组/伪数组
   * @param {Function} fn 遍历函数
   */
  function each (arr, fn) {
    if (arr && arr.length && arr.length > 0 && fn && typeof fn === 'function') {
      for (let i = 0; i < arr.length; i++) {
        const b = fn(arr[i], i, arr)
        if (b === false) {
          return false
        }
      }
    }
  }

  /**
   * 数组查找（单个）
   * @param {Array} arr 数组/伪数组
   * @param {Function} fn 检测函数
   */
  function searchOne (arr, fn) {
    let rst = null
    if (arr && fn) {
      each(arr, function (item, i) {
        if (fn(item, i, arr)) {
          rst = item
          return false
        }
      })
    }
    return rst
  }

  /**
   * 数组查找（多个）
   * @param {Array} arr 数组/伪数组
   * @param {Function} fn 检测函数
   */
  function searchList (arr, fn) {
    const rst = []
    if (arr && fn) {
      each(arr, function (item, i) {
        if (fn(item, i, arr)) {
          rst.push(item)
        }
      })
    }
    return rst
  }

  /**
   * 树形遍历
   * @param {Array} arr 数组对象
   * @param {Function} fn 遍历函数
   * @param {Object} p 上一节点
   * @param {String} tf 树节点属性，默认值：children
   */
  function treeEach (arr, fn, p, tf) {
    tf = tf || 'children'
    return each(arr, function (item, i) {
      let b = fn(item, p, i)
      if (b !== false) {
        b = treeEach(item[tf], fn, item, tf)
      }
      return b
    })
  }

  /**
   * 树形查找（单个）
   * @param {Array} arr 数组对象
   * @param {Function} fn 检测函数
   * @param {String} tf 树节点属性，默认值：children
   * @returns {Object} 结果对象
   */
  function treeOne (arr, fn, tf) {
    let rst = null
    if (arr && fn) {
      treeEach(arr, function (item, p, i) {
        if (fn(item, p, i)) {
          rst = item
          return false
        }
      }, null, tf)
    }
    return rst
  }

  /**
   * 树形查找（多个）
   * @param {Array} arr 数组对象
   * @param {Function} fn  检测函数
   * @param {String} tf 树节点属性，默认值：children
   * @returns {Array} 结果数组
   */
  function treeList (arr, fn, tf) {
    const rst = []
    if (arr && fn) {
      treeEach(arr, function (item, p, i) {
        if (fn(item, p, i)) {
          rst.push(item)
        }
      }, null, tf)
    }
    return rst
  }

  /**
   * 获取父节点
   * @param {Array} arr 数组对象
   * @param {Object} node 节点
   * @param {String} kf 树节点唯一标识字段，默认值：id
   * @param {String} tf  树节点属性，默认值：children
   * @returns {Object} 父节点
   */
  function treeParent (arr, node, kf, tf) {
    kf = kf || 'id'
    let rst = null
    if (arr && node) {
      treeEach(arr, function (item, p, i) {
        if (item[kf] == node[kf]) {
          rst = p
          return false
        }
      }, null, tf)
    }
    return rst
  }

  /**
   * 获取父代所有节点
   * @param {Array} arr 数组对象
   * @param {Object} node 节点
   * @param {String} kf 树节点唯一标识字段，默认值：id
   * @param {String} tf 树节点属性，默认值：children
   * @returns {Array} 父代所有节点，直到根节点
   */
  function treeParents (arr, node, kf, tf) {
    kf = kf || 'id'
    let parent = null
    const rst = []
    const map = {}
    if (arr && node) {
      treeEach(arr, function (item, p, i) {
        map[item[kf]] = p
        if (item[kf] == node[kf]) {
          parent = p
          return false
        }
      }, null, tf)
    }
    while (parent) {
      if (rst.indexOf(parent) != -1) {
        break
      }
      rst.push(parent)
      parent = map[parent[kf]]
    }
    return rst
  }

  /**
   * 获取父代节点路径
   * @param {Array} arr 数组对象
   * @param {Object} node 节点
   * @param {String|Function} lf 节点标签字段，默认值：name
   * @param {String} kf 节点唯一标识字段，默认值：id
   * @param {String} tf 树节点属性，默认值：children
   * @returns {String} 路径字符串
   */
  function treeParentPath (arr, node, lf, kf, tf) {
    lf = lf || 'name'
    let path = ''
    if (arr && node) {
      const separator = '/'
      const items = treeParents(arr, node, kf, tf)
      for (var i = items.length - 1; i >= 0; i--) {
        const item = items[i]
        if (typeof lf === 'function') {
          path += separator + lf(item)
        } else {
          path += separator + item[lf]
        }
      }
      path = path || separator
    }
    return path
  }

  /**
   * 获取节点路径
   * @param {Array} arr 数组对象
   * @param {Object} node 节点
   * @param {String|Function} lf 节点标签字段，默认值：name
   * @param {String} kf 节点唯一标识字段，默认值：id
   * @param {String} tf 树节点属性，默认值：children
   * @returns {String} 路径字符串
   */
  function treeNodePath (arr, node, lf, kf, tf) {
    lf = lf || 'name'
    let path = ''
    if (arr && node) {
      const separator = '/'
      const items = treeParents(arr, node, kf, tf)
      for (var i = items.length - 1; i >= 0; i--) {
        const item = items[i]
        if (typeof lf === 'function') {
          path += separator + lf(item)
        } else {
          path += separator + item[lf]
        }
      }
      if (typeof lf === 'function') {
        path += separator + lf(node)
      } else {
        path += separator + node[lf]
      }
    }
    return path
  }

  /**
   * 查找一个节点数据
   * @param {Array} arr  数组对象
   * @param {String} keyValue 用来比对的键值
   * @param {String} keyField 用来比对的属性，默认值：id
   */
  function findOne (arr, keyValue, keyField) {
    keyField = keyField || 'id'
    var data = treeOne(arr, function (item, p, i) {
      if (item[keyField] == keyValue) {
        return true
      }
    })
    return data
  }

  /**
   * 对象扩展，用法：extend( [deep ], target, object1 [, objectN ] )
   * @param {Boolean} deep 是否深度合并，可选
   * @param {Object} target 目标对象，其他对象成员的属性都将附加到该对象上
   * @param {Object} object1 第1个被合并的对象
   * @param {Object} objectN 第N个被合并的对象
   * @returns {Object} 结果数组
   */
  function extend () {
    var options
    var name
    var src
    var copy
    var copyIsArray
    var clone
    var target = arguments[0] || {}
    var i = 1
    var length = arguments.length
    var deep = false

    if (typeof target === 'boolean') {
      deep = target
      target = arguments[i] || {}
      i++
    }

    if (typeof target !== 'object' && !obj.isFunction(target)) {
      target = {}
    }

    if (i === length) {
      target = this
      i--
    }

    for (; i < length; i++) {
      options = arguments[i]
      if (options == null || options == undefined) {
        continue
      }
      for (name in options) {
        copy = options[name]

        if (name === '__proto__' || target === copy) {
          continue
        }

        if (deep && copy && (obj.isPlainObject(copy) ||
          (copyIsArray = Array.isArray(copy)))) {
          src = target[name]

          if (copyIsArray && !Array.isArray(src)) {
            clone = []
          } else if (!copyIsArray && !obj.isPlainObject(src)) {
            clone = {}
          } else {
            clone = src
          }
          copyIsArray = false
          target[name] = extend(deep, clone, copy)
        } else if (copy !== undefined) {
          target[name] = copy
        }
      }
    }

    return target
  }

  /**
   * 深度克隆对象
   * @param {Object} item 被克隆的对象
   * @returns {Object} 克隆后的新对象
   */
  function clone (item) {
    return extend(true, {}, item)
  }

  /**
   * 是否为空白值
   * @param {String} value
   */
  function isBlank (value) {
    return obj.isUndefined(value) || obj.isNull(value) || (obj.isString(value) && /^[\s]*$/.test(value))
  }

  /**
   * 取值
   * @param {Object} source 数据源
   * @param {String} key 属性值，多个使用.隔开
   */
  function sourceValue (source, key) {
    let result = null
    if (obj.isObject(source) && obj.isString(key) && key) {
      var arr = key.split('.')
      for (let i = 0; i < arr.length; i++) {
        source = source[arr[i]]
        if (!obj.isObject(source) && !obj.isLocation(source) && i < arr.length - 1) {
          source = null
          break
        }
        if (i == arr.length - 1) {
          result = source
        }
      }
    }
    return result
  }

  /**
   * 对El表达式求值
   * @param {String} expr el表达式
   * @param {Object} source 数据源
   */
  function elValue (expr, source) {
    if (!expr || !obj.isString(expr)) return ''
    const regex = new RegExp('(\\$\\{([^\\}]*)\\})', 'g')
    let arr
    let startIndex = 0
    let newText = ''
    while ((arr = regex.exec(expr)) !== null) {
      newText += expr.substring(startIndex, arr.index)
      newText += arr[2] ? sourceValue(source, arr[2]) || '' : ''
      startIndex = regex.lastIndex
    }
    if (startIndex < expr.length) {
      newText += expr.substring(startIndex, expr.length)
    }
    return newText
  }

  obj.extend = extend
  obj.clone = clone
  obj.each = each
  obj.searchOne = searchOne
  obj.searchList = searchList
  obj.treeEach = treeEach
  obj.treeOne = treeOne
  obj.treeList = treeList
  obj.treeParent = treeParent
  obj.treeParents = treeParents
  obj.treeParentPath = treeParentPath
  obj.treeNodePath = treeNodePath
  obj.findOne = findOne
  obj.isBlank = isBlank
  obj.elValue = elValue
  obj.sourceValue = sourceValue
})(cdata)

// 日志处理
const clog = util || {};
(function (obj, win) {
  function call (fn, arg) {
    if (win && win.console && win.console[fn] && typeof win.console[fn] === 'function') {
      win.console[fn].apply(win.console, Array.prototype.slice.call(arg))
    }
  }

  obj.warn = function () {
    call('warn', arguments)
  }

  obj.error = function () {
    call('error', arguments)
  }

  obj.debug = function () {
    call('debug', arguments)
  }

  obj.info = function () {
    call('info', arguments)
  }

  obj.log = function () {
    call('log', arguments)
  }
})(clog, window)

// 数据转换
const cfmt = util || {};
(function (obj) {
  /**
   * 日期格式化
   * @param {Date} date 日期
   * @param {String} fmt 格式化字符串，如 yyyy-MM-dd HH:mm:ss.S
   */
  function datefmt (date, fmt) {
    if (obj.isString(date)) {
      date = parseDate(date)
    }
    var o = {
      'M+': date.getMonth() + 1, // 月份
      'd+': date.getDate(), // 日期
      'H+': date.getHours(), // 小时
      'm+': date.getMinutes(), // 分
      's+': date.getSeconds(), // 秒
      'q+': Math.floor((date.getMonth() + 3) / 3), // 季度
      S: date.getMilliseconds() // 毫秒
    }
    if (/(y+)/.test(fmt)) {
      fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length))
    }
    for (var k in o) {
      if (new RegExp('(' + k + ')').test(fmt)) {
        fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length)))
      }
    }
    return fmt
  }

  function listfmt (key, list, keyField, valueField) {
    keyField = keyField || 'id'
    valueField = valueField || 'name'
    const r = obj.searchOne(list, function (item) {
      return item[keyField] == key
    })
    return r ? r[valueField] : key
  }

  function treefmt (key, tree, keyField, valueField) {
    keyField = keyField || 'id'
    valueField = valueField || 'name'
    const r = obj.treeOne(tree, function (item) {
      return item[keyField] == key
    })
    return r ? r[valueField] : key
  }

  function decode () {
    if (arguments.length == 0) return undefined
    const v = arguments[0]
    const len = arguments.length
    const size = (len - 1) / 2
    const mode = (len - 1) % 2
    for (let i = 1; i <= size; i++) {
      if (v === arguments[i * 2 - 1]) return arguments[i * 2]
    }
    if (mode) return arguments[len - 1]
    return undefined
  }

  function nvl () {
    if (arguments.length == 0) return undefined
    for (let i = 0; i < arguments.length; i++) {
      const v = arguments[i]
      if (v === 0 || v === false || v) return v
    }
    return arguments[arguments.length - 1]
  }

  const isoRegex = /^((-\d+|\d{4,})(-(\d{2})(-(\d{2}))?)?)?(T| )((\d{2})(:(\d{2})(:(\d{2})(\.(\d{1,3})(\d)?\d*)?)?)?)?(([+-])(\d{2})((\d{2}))?|Z)?$/
  const numRegex = /[^0-9]/

  function parseDate (str) {
    let date = null
    if (!str) {
      return date
    }
    const matches = str.match(isoRegex)
    if (!matches) {
      var arr = str.split(numRegex)
      var da = []
      for (var i = 0; i < arr.length; i++) {
        if (!arr[i]) continue
        da.push(parseInt(arr[i]))
      }
      da.push.apply(da, [0, 0, 0, 0, 0, 0, 0])
      date = new Date(da[0], da[1], da[2], da[3], da[4], da[5], da[6])
    } else {
      date = new Date(0)
      const methods = {
        FullYear: 2,
        Month: 4,
        Date: 6,
        Hours: 9,
        Minutes: 11,
        Seconds: 13,
        Milliseconds: 15
      }
      // 处理月份
      if (matches[4]) matches[4]--
      // 处理毫秒数
      if (matches[16] >= 5) matches[15]++
      const utc = matches[17] || matches[19] ? 'UTC' : ''
      for (var i in methods) {
        var v = matches[methods[i]]
        if (!v) continue
        date['set' + utc + i](v)
      }
      if (matches[19]) {
        var h = Number(matches[18] + matches[19])
        var m = Number(matches[18] + (matches[21] || 0))
        date.setUTCMinutes(date.getUTCMinutes() - (h * 60) - m)
      }
    }
    return date
  }

  obj.datefmt = datefmt
  obj.listfmt = listfmt
  obj.treefmt = treefmt
  obj.decode = decode
  obj.nvl = nvl
  obj.parseDate = parseDate
})(cfmt)

// Dom节点
const cdom = util || {};
(function (obj) {
  function findParentElement (el, tagName) {
    while (el && tagName && el != document.body) {
      if (el.tagName.toLowerCase() == tagName.toLowerCase()) {
        return el
      }
      el = el.parentElement
    }
  }

  obj.elp = findParentElement
})(cdom)

const cquery = util || {};
(function (obj) {
  function getQueryParam (name) {
    if (!location || !location.search) {
      return null
    }
    var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i')
    var r = location.search.substring(1).match(reg)
    if (r != null) {
      return decodeURIComponent(r[2])
    }
    return null
  }

  function queryString2Object (queryString) {
    var params = {}
    var arr = queryString.split('&')
    for (var i = 0; i < arr.length; i++) {
      var parr = arr[i].split('=')
      params[parr[0]] = decodeURIComponent(parr[1])
    }
    return params
  }

  function getQueryObject () {
    if (!location || !location.search) return {}
    return queryString2Object(location.search.substring(1))
  }

  function addQueryParam (url, param) {
    let search, hash
    if (url.indexOf('#') != -1) {
      hash = url.substr(url.indexOf('#') + 1)
      url = url.substr(0, url.indexOf('#'))
    }
    if (url.indexOf('?') != -1) {
      search = url.substr(url.indexOf('?') + 1)
      url = url.substr(0, url.indexOf('?'))
    }
    search = search || ''
    for (const n in param) {
      search += '&' + n + '=' + encodeURIComponent(param[n])
    }
    if (search.indexOf('&') == 0) search = search.substring(1)
    if (search) url += '?' + search
    if (hash) url += '#' + hash
    return url
  }

  function mergeQueryParam (url, param) {
    if (!param) return url
    let search, hash
    if (url.indexOf('#') != -1) {
      hash = url.substr(url.indexOf('#'))
      url = url.substr(0, url.indexOf('#'))
    }
    if (url.indexOf('?') != -1) {
      search = url.substr(url.indexOf('?') + 1)
      url = url.substr(0, url.indexOf('?'))
    }
    const param2 = search ? queryString2Object(search) : {}
    for (const n in param2) {
      param[n] = param2[n]
    }
    search = ''
    for (const n in param) {
      search += '&' + n + '=' + encodeURIComponent(param[n])
    }
    if (search.indexOf('&') == 0) search = search.substring(1)
    if (search) url += '?' + search
    if (hash) url += hash
    return url
  }

  obj.getQueryParam = getQueryParam
  obj.getQueryObject = getQueryObject
  obj.addQueryParam = addQueryParam
  obj.mergeQueryParam = mergeQueryParam
})(cquery)

const cpath = util || {};
(function (obj) {
  function pathConcat (path1, path2) {
    let path = null
    if (!path1 || !path2) {
      path = path1 || path2
    } else {
      if (/^\.\//.test(path2)) {
        path2 = path2.replace('./', '')
      }
      while (/^\.\.\//.test(path2) && !/^[\s]*$/.test(path1)) {
        path2 = path2.replace('../', '')
        if (path1.lastIndexOf('/') == -1) {
          path1 = ''
        } else {
          path1 = path1.substr(0, path1.lastIndexOf('/'))
        }
      }
      path = path1 + path2
    }
    return path
  }

  function pathConcats () {
    let path = null
    if (arguments) {
      for (let i = 0; i < arguments.length; i++) {
        path = pathConcat(path, arguments[i])
      }
    }
    return path
  }

  obj.pathConcats = pathConcats
})(cpath)

const cfilter = util || {};
(function (obj) {
  let filterConfig

  function setFilterConfig (config) {
    filterConfig = config
  }

  function getFilterConfig () {
    return filterConfig
  }

  function matchItem (item, matchObject) {
    let result = false
    const text = obj.elValue(item.text, matchObject)
    if (item.type == 'text') {
      if (item.expr && text && text.indexOf(item.expr) != -1) {
        result = true
      }
    } else if (item.type == 'regex') {
      const regex = new RegExp(item.expr)
      if (regex.test(text)) {
        result = true
      }
    }
    return result
  }

  function matchFilter (filter, matchObject) {
    let result = false
    if (filter && filter.matches && filter.matches.length > 0) {
      for (let i = 0; i < filter.matches.length; i++) {
        const item = filter.matches[i]
        if (matchItem(item, matchObject)) {
          result = true
        } else {
          result = false
          break
        }
      }
    }
    return result
  }

  function replaceText (filter, text) {
    if (filter && filter.oldText && filter.newText) {
      text = text.replace(filter.oldText, filter.newText)
    }
    return text
  }

  function replaceString (value, filters) {
    for (let i = 0; i < filters.length; i++) {
      value = replaceText(filters[i], value)
    }
    return value
  }

  function replaceObject (value, filters) {
    for (const n in value) {
      const item = value[n]
      if (obj.isString(item)) {
        value[n] = replaceString(item, filters)
      } else if (obj.isObject(item)) {
        value[n] = replaceObject(item, filters)
      } else if (obj.isArray(item)) {
        value[n] = replaceArray(item, filters)
      }
    }
    return value
  }

  function replaceArray (value, filters) {
    for (let i = 0; i < value.length; i++) {
      const item = value[i]
      if (obj.isString(item)) {
        value[i] = replaceString(item, filters)
      } else if (obj.isObject(item)) {
        value[i] = replaceObject(item, filters)
      } else if (obj.isArray(item)) {
        value[i] = replaceArray(item, filters)
      }
    }
    return value
  }

  function replaceValue (value, filters, matchObject) {
    let result = value
    if (obj.isString(filters) && filterConfig) {
      filters = filterConfig[filters]
    }

    if (obj.isArray(filters)) {
      const arr = []
      matchObject = util.extend(true, {}, matchObject)
      matchObject.location = location
      for (let i = 0; i < filters.length; i++) {
        const filter = filters[i]
        if (matchFilter(filter, matchObject)) {
          arr.push(filter)
        }
      }
      filters = arr
    }
    if (!value || !obj.isArray(filters) || filters.length == 0) {
      result = value
    } else if (obj.isString(value)) {
      result = replaceString(value, filters)
    } else if (obj.isArray(value)) {
      result = replaceArray(value, filters)
    } else if (obj.isObject(value)) {
      result = replaceObject(value, filters)
    }
    return result
  }

  obj.replaceValue = replaceValue
  obj.setFilterConfig = setFilterConfig
  obj.getFilterConfig = getFilterConfig
})(cfilter)

export default util
